<!DOCTYPE html>
<html>
  <head>
    <title>加密 HLS 播放器 (DRM)</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        padding: 20px;
        max-width: 1000px;
        margin: 0 auto;
        background-color: #f5f5f5;
      }
      .container {
        background-color: white;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
      }
      h1 {
        color: #333;
        border-bottom: 2px solid #eee;
        padding-bottom: 10px;
      }
      video {
        width: 100%;
        max-width: 800px;
        border-radius: 4px;
        margin: 20px 0;
      }
      button {
        background-color: #4caf50;
        border: none;
        color: white;
        padding: 10px 15px;
        text-align: center;
        text-decoration: none;
        display: inline-block;
        font-size: 14px;
        margin: 4px 8px 4px 0;
        cursor: pointer;
        border-radius: 4px;
        transition: background-color 0.3s;
      }
      button:hover {
        background-color: #45a049;
      }
      button:disabled {
        background-color: #cccccc;
        cursor: not-allowed;
      }
      .status {
        margin: 15px 0;
        padding: 10px;
        background-color: #f0f0f0;
        border-radius: 4px;
        font-size: 14px;
      }
      .error {
        color: #d32f2f;
        background-color: #ffebee;
        padding: 10px;
        border-radius: 4px;
        margin: 10px 0;
        display: none;
      }
      .version {
        font-size: 12px;
        color: #666;
        margin-top: 5px;
      }
      .login-form {
        margin: 20px 0;
        padding: 15px;
        background-color: #f9f9f9;
        border-radius: 4px;
        border: 1px solid #ddd;
      }
      .form-group {
        margin-bottom: 10px;
      }
      .form-group label {
        display: block;
        margin-bottom: 5px;
        font-weight: bold;
      }
      .form-group input {
        width: 100%;
        padding: 8px;
        border: 1px solid #ddd;
        border-radius: 4px;
        box-sizing: border-box;
      }
      .auth-status {
        padding: 10px;
        margin: 10px 0;
        border-radius: 4px;
      }
      .auth-status.logged-in {
        background-color: #e8f5e9;
        color: #2e7d32;
      }
      .auth-status.logged-out {
        background-color: #fafafa;
        color: #757575;
      }
      .hidden {
        display: none;
      }
      .token-info {
        font-size: 12px;
        color: #666;
        margin-top: 10px;
        word-break: break-all;
      }
      .controls {
        margin: 15px 0;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>加密 HLS 播放器 (DRM)</h1>
      <p>
        这个播放器演示了如何播放使用 AES-128 加密的 HLS
        视频内容，并通过类似DRM的授权系统进行密钥管理
      </p>
      <div class="version">HLS.js 版本: 1.1.5</div>

      <!-- 认证区域 -->
      <div id="auth-section">
        <div id="login-form" class="login-form">
          <h3>用户登录</h3>
          <div class="form-group">
            <label for="username">用户名</label>
            <input type="text" id="username" value="admin" />
          </div>
          <div class="form-group">
            <label for="password">密码</label>
            <input type="password" id="password" value="admin" />
          </div>
          <button onclick="login()">登录</button>
        </div>

        <div id="auth-status" class="auth-status logged-out">
          <span id="auth-message">未登录</span>
          <button id="logout-btn" onclick="logout()" style="display: none">登出</button>
        </div>

        <div id="token-info" class="token-info hidden"></div>
      </div>

      <div class="controls">
        <button id="play-btn" onclick="playVideo()" disabled>播放加密视频</button>
      </div>

      <div id="status" class="status">状态: 等待登录</div>
      <div id="error" class="error"></div>

      <video id="video" width="800" height="450" controls></video>
    </div>

    <!-- 使用特定版本的HLS.js，避免最新版可能存在的兼容性问题 -->
    <!-- <script src="https://cdn.jsdelivr.net/npm/hls.js@1.1.5"></script> -->
    <script src="/js/hls.js"></script>
    <script>
      // DRM系统配置
      const DRM_CONFIG = {
        LICENSE_SERVER: "http://localhost:3000",
        AUTH_ENDPOINT: "/drm/auth/token",
        KEY_ENDPOINT: "/drm/key",
        LICENSE_INFO_ENDPOINT: "/drm/info"
      }

      // DOM元素
      const video = document.getElementById("video")
      const statusElement = document.getElementById("status")
      const errorElement = document.getElementById("error")
      const playButton = document.getElementById("play-btn")
      const authStatusElement = document.getElementById("auth-status")
      const authMessageElement = document.getElementById("auth-message")
      const logoutButton = document.getElementById("logout-btn")
      const loginForm = document.getElementById("login-form")
      const tokenInfoElement = document.getElementById("token-info")

      // 状态变量
      let hls
      let authToken = null
      let tokenExpiry = null

      // 检查浏览器是否支持 HLS.js
      const isHlsSupported = Hls.isSupported()

      // 页面加载时检查本地存储的令牌
      window.onload = function () {
        // checkStoredToken()
        logout()
      }

      // 检查本地存储的令牌
      function checkStoredToken() {
        const storedToken = localStorage.getItem("drm_token")
        const storedExpiry = localStorage.getItem("drm_token_expiry")

        if (storedToken && storedExpiry) {
          const expiryDate = new Date(parseInt(storedExpiry))
          const now = new Date()

          if (expiryDate > now) {
            // 令牌仍然有效
            authToken = storedToken
            tokenExpiry = expiryDate
            updateAuthStatus(true)
            return true
          } else {
            // 令牌已过期，清除存储
            localStorage.removeItem("drm_token")
            localStorage.removeItem("drm_token_expiry")
          }
        }

        updateAuthStatus(false)
        return false
      }

      // 用户登录
      function login() {
        const username = document.getElementById("username").value
        const password = document.getElementById("password").value

        if (!username || !password) {
          showError("请输入用户名和密码")
          return
        }

        updateStatus("正在验证用户凭据...")

        // 发送登录请求
        fetch(`${DRM_CONFIG.LICENSE_SERVER}${DRM_CONFIG.AUTH_ENDPOINT}`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json"
          },
          body: JSON.stringify({ username, password })
        })
          .then(response => {
            if (!response.ok) {
              throw new Error(`认证失败: ${response.status}`)
            }
            return response.json()
          })
          .then(data => {
            // 保存令牌
            authToken = data.token

            // 计算过期时间
            const expiryTime = Date.now() + data.expires_in * 1000
            tokenExpiry = new Date(expiryTime)

            // 保存到本地存储
            localStorage.setItem("drm_token", authToken)
            localStorage.setItem("drm_token_expiry", expiryTime.toString())

            updateStatus("登录成功")
            updateAuthStatus(true)

            // 显示令牌信息
            const tokenPreview = `${authToken.substring(0, 20)}...`
            tokenInfoElement.textContent = `令牌: ${tokenPreview} (过期时间: ${tokenExpiry.toLocaleString()})`
            tokenInfoElement.classList.remove("hidden")
          })
          .catch(error => {
            showError(`登录失败: ${error.message}`)
            updateAuthStatus(false)
          })
      }

      // 用户登出
      function logout() {
        authToken = null
        tokenExpiry = null

        // 清除本地存储
        localStorage.removeItem("drm_token")
        localStorage.removeItem("drm_token_expiry")

        updateAuthStatus(false)
        updateStatus("已登出")

        // 如果正在播放，停止播放
        if (hls) {
          hls.destroy()
          hls = null
          video.src = ""
        }
      }

      // 更新认证状态UI
      function updateAuthStatus(isLoggedIn) {
        if (isLoggedIn) {
          authStatusElement.className = "auth-status logged-in"
          authMessageElement.textContent = "已登录"
          logoutButton.style.display = "inline-block"
          loginForm.style.display = "none"
          playButton.disabled = false
        } else {
          authStatusElement.className = "auth-status logged-out"
          authMessageElement.textContent = "未登录"
          logoutButton.style.display = "none"
          loginForm.style.display = "block"
          playButton.disabled = true
          tokenInfoElement.classList.add("hidden")
        }
      }

      // 初始化 HLS 播放器
      function initPlayer() {
        if (hls) {
          hls.destroy()
          hls = null
        }

        if (isHlsSupported) {
          try {
            hls = new Hls({
              debug: false,
              // 增加缓冲区配置，减少缓冲错误
              maxBufferLength: 30, // 最大缓冲区长度（秒）
              maxMaxBufferLength: 600, // 最大最大缓冲区长度（秒）
              maxBufferSize: 60 * 1000 * 1000, // 最大缓冲区大小（字节）
              maxBufferHole: 0.5, // 最大缓冲区空洞（秒）
              lowLatencyMode: false, // 关闭低延迟模式
              highBufferWatchdogPeriod: 3, // 高缓冲区监视器周期
              nudgeOffset: 0.2, // 调整偏移
              nudgeMaxRetry: 5, // 最大调整重试次数
              startFragPrefetch: true, // 预取起始片段
              // 错误恢复配置
              fragLoadingMaxRetry: 6, // 片段加载最大重试次数
              manifestLoadingMaxRetry: 6, // 清单加载最大重试次数
              levelLoadingMaxRetry: 6, // 级别加载最大重试次数
              xhrSetup: function (xhr, url) {
                // 为密钥请求添加授权头
                if (url.includes(DRM_CONFIG.KEY_ENDPOINT)) {
                  if (authToken) {
                    xhr.setRequestHeader("Authorization", `Bearer ${authToken}`)
                    console.log("已添加授权头到密钥请求")
                  } else {
                    console.warn("没有授权令牌，密钥请求可能会失败")
                  }
                }
              }
            })

            // 设置HLS事件监听器
            setupHlsEvents()

            hls.attachMedia(video)
            return true
          } catch (error) {
            console.error("初始化HLS播放器失败:", error)
            showError(`初始化播放器失败: ${error.message}`)
            return false
          }
        } else {
          showError("您的浏览器不支持 HLS.js，请尝试使用 Safari 或其他支持 HLS 的浏览器")
          return false
        }
      }

      // 播放加密视频
      function playVideo() {
        // 检查令牌是否有效
        if (!authToken) {
          showError("请先登录")
          return
        }

        clearError()
        const encryptedUrl = "/drm/index.m3u8"
        updateStatus("正在加载加密视频...")

        if (initPlayer()) {
          if (isHlsSupported) {
            hls.loadSource(encryptedUrl)
          } else if (video.canPlayType("application/vnd.apple.mpegurl")) {
            // 原生HLS支持(Safari)
            // 注意：这种方式无法添加授权头，可能无法正常工作
            video.src = encryptedUrl
            video.addEventListener("loadedmetadata", function () {
              video.play()
            })
          }
        }
      }

      // 设置HLS事件监听器
      function setupHlsEvents() {
        // 监听错误
        hls.on(Hls.Events.ERROR, function (event, data) {
          console.error("HLS错误:", data)

          if (data.fatal) {
            switch (data.type) {
              case Hls.ErrorTypes.NETWORK_ERROR:
                // 检查是否是密钥加载错误
                if (data.details === Hls.ErrorDetails.KEY_LOAD_ERROR) {
                  showError("密钥加载失败，可能是授权问题。请确保您已登录并有权访问此内容。")
                } else {
                  showError(`网络错误，尝试恢复... 详情: ${data.details}`)
                  hls.startLoad()
                }
                break
              case Hls.ErrorTypes.MEDIA_ERROR:
                showError(`媒体错误，尝试恢复... 详情: ${data.details}`)
                hls.recoverMediaError()
                break
              default:
                showError(
                  `无法恢复的错误: ${data.details}, 原因: ${
                    data.error ? data.error.message : "未知"
                  }`
                )
                break
            }
          } else {
            console.warn("非致命错误:", data)
            // 对于缓冲停滞错误，尝试特定的恢复策略
            if (data.details === Hls.ErrorDetails.BUFFER_STALLED_ERROR) {
              updateStatus(`正在处理缓冲停滞，尝试恢复播放...`)
              // 可以尝试重新加载当前片段
              setTimeout(() => {
                if (hls) {
                  const currentTime = video.currentTime
                  // 稍微调整播放位置以触发新的加载
                  video.currentTime = currentTime - 0.1
                }
              }, 1000)
            } else {
              updateStatus(`处理非致命错误: ${data.details}`)
            }
          }
        })

        hls.on(Hls.Events.MANIFEST_PARSED, function () {
          updateStatus("视频已加载，准备播放")
          video.play().catch(e => {
            updateStatus("自动播放失败，请点击视频播放")
            console.warn("自动播放被阻止:", e)
          })
        })

        hls.on(Hls.Events.KEY_LOADING, function (event, data) {
          updateStatus("正在加载解密密钥...")
          console.log("密钥加载中:", data)
        })

        hls.on(Hls.Events.KEY_LOADED, function (event, data) {
          updateStatus("解密密钥已加载")
          console.log("密钥已加载:", data)
        })

        // 添加缓冲监控
        hls.on(Hls.Events.BUFFER_APPENDED, function () {
          const buffered = video.buffered
          if (buffered.length > 0) {
            const bufferedEnd = buffered.end(buffered.length - 1)
            const duration = video.duration
            const bufferedPercent = ((bufferedEnd / duration) * 100).toFixed(2)
            updateStatus(
              `已缓冲: ${bufferedPercent}% (${bufferedEnd.toFixed(2)}/${duration.toFixed(2)}秒)`
            )
          }
        })

        // 添加更多调试信息
        hls.on(Hls.Events.FRAG_LOADING, function (event, data) {
          updateStatus(`正在加载视频片段 #${data.frag.sn}...`)
        })

        hls.on(Hls.Events.FRAG_LOADED, function (event, data) {
          updateStatus(`视频片段 #${data.frag.sn} 已加载`)
        })

        // 监听播放质量
        video.addEventListener("waiting", function () {
          updateStatus("视频缓冲中...")
        })

        video.addEventListener("playing", function () {
          updateStatus("视频播放中")
        })
      }

      // 更新状态显示
      function updateStatus(message) {
        statusElement.textContent = "状态: " + message
        console.log("状态更新:", message)
      }

      // 显示错误
      function showError(message) {
        errorElement.textContent = "错误: " + message
        errorElement.style.display = "block"
        console.error("显示错误:", message)
      }

      // 清除错误
      function clearError() {
        errorElement.textContent = ""
        errorElement.style.display = "none"
      }
    </script>
  </body>
</html>
